package strategies;

import java.util.ArrayList;
import java.util.HashMap;
import java.util.HashSet;
import java.util.List;
import java.util.Map;
import java.util.Set;

import stateInfo.Cell;
import stateInfo.GameState;
import utilities.HOF;
import utilities.Logger;

import com.google.common.base.Function;

public class Diffuser {
  GameState gameState;
  
  public Diffuser(GameState gameStateIn) {
    gameState = gameStateIn;
  }
   
  private void setBoardValues(double[][] board, Map<Cell, Integer> pathLengths, double goalValue){

    for(Cell c: pathLengths.keySet( )){

    	double cellChange = goalValue / (Math.pow(pathLengths.get(c), 2));
    	//Logger.log(goalValue + " / " + pathLengths.get(c) + " squared  = " + cellChange);
    	
    	board[c.row][c.col] += cellChange;
      //Logger.log("changing value of " + c.row + " " + c.col + " by "  + cellChange + " to " + board[c.row][c.col]);
    }
  }
  
  public void diffuse(double[][] board, Cell startCell, 
                      double sensitivityThreshold, Function<Cell, Double> getWeight) {
    
    double goalValue = getWeight.apply(startCell);
    double currValue = goalValue;
    
    List<Cell> frontier = new ArrayList<Cell>( );
    frontier.add(startCell);
    Set<Cell> seen = new HashSet<Cell>( );
    Map<Cell, Cell> predecessors = new HashMap<Cell, Cell>( );
    Map<Cell, Integer> pathLengths = new HashMap<Cell, Integer>( );
    pathLengths.put(startCell, 1);
    
    
    pathLengths = diffuseRecursive(board, frontier, seen, sensitivityThreshold,
                                   new HOF.Function_1<Cell, Boolean>( ) {
                                     public Boolean apply(Cell m) {
                                       return (gameState.isWater(m) || gameState.isMyAnt(m));
                                     }
                                     
                                   }, predecessors, pathLengths, goalValue);
   // for(Cell c: pathLengths.keySet()) Logger.log(c + "pathlength: " + pathLengths.get(c));
    
    setBoardValues(board, pathLengths, goalValue);
  }
  
  private Map<Cell, Integer> diffuseRecursive(double[][] board,
                                              
                                              List<Cell> frontier, // sometimes
                                                                   // called
                                              Set<Cell> seen, // sometimes
                                                              // called "closed"
                                              double sensitivityThreshold,
                                              HOF.Function_1<Cell, Boolean> isBlocked,
                                              Map<Cell, Cell> predecessors,
                                              Map<Cell, Integer> pathLengths, double goalValue) {
    
    
    if (frontier.isEmpty( )) {
     
      return pathLengths;
    }
    
    Cell currNode = frontier.remove(0);
    
    // Logger.log("removed " + currNode.row + " " + currNode.col +
    // " from frontier");
    List<Cell> newFrontier = new ArrayList<Cell>( );
    newFrontier.addAll(frontier);
    for (Cell m: gameState.getLandNeighbors(currNode))
      if (!(seen.contains(m))) {
        seen.add(m);
        predecessors.put(m, currNode);
        if (!(isBlocked.apply(m))) {
          newFrontier.add(m);
          //Logger.log("adding " + m.row + " " + m.col + " to frontier");
          
          pathLengths.put(m, pathLengths.get(currNode) + 1);
        }
      }
      else if (pathLengths.containsKey(m)) {
        int length = pathLengths.get(m);
        int newLength = pathLengths.get(currNode) + 1;
        if (length > newLength) {
          pathLengths.remove(m);
          pathLengths.put(m, newLength);
        }
      }
    
    
    Set<Cell> newSeen = new HashSet<Cell>( );
    newSeen.addAll(seen);
    newSeen.add(currNode);
    
    double currScentMag = Math.abs(goalValue) / Math.pow((pathLengths.get(currNode) -1), 2);
    
     if (currScentMag >= sensitivityThreshold) {
        return diffuseRecursive(board, newFrontier, newSeen,
              sensitivityThreshold,
              new HOF.Function_1<Cell, Boolean>( ) {
              public Boolean apply(Cell m) {
              return (gameState.isWater(m) || gameState.isMyAnt(m));
              }
              }, predecessors, pathLengths, goalValue);
    
    }
    return pathLengths;
  }
  
}
